﻿using System;
using System.Collections.Generic;
using Heaven.Geometry;
using Heaven.Graphics;
using System.IO;
using System.Reflection;
using Microsoft.Xna.Framework.Graphics;

namespace Heaven.Xna.Graphics
{
    /// <summary>
    /// Caches device representaions of shader objects
    /// </summary>
    class ShaderManager
    {
        #region Fields

        // Graphics device
        GraphicsDevice device = null;
        // Cache entity
        Dictionary<long, Effect> cache = new Dictionary<long, Effect>();

        // For fast access to the object
        long lastUsedHash = 0;
        Effect lastUsedEffect = null;

        #endregion

        #region Properties

        /// <summary>
        /// Count of shaders
        /// </summary>
        public int Count
        {
            get { return cache.Count; }
        }

        /// <summary>
        /// Shader - representation for objects with errors
        /// </summary>
        private string ErrorShaderSource
        {
            get
            {
                using (Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(
                       "Heaven.Xna.Resources.Shaders.Error.fx"))
                {
                    StreamReader reader = new StreamReader(stream);
                    return reader.ReadToEnd();
                }
            }
        }

        #endregion

        #region Initialization

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="device">Graphics device</param>
        public ShaderManager(GraphicsDevice device) 
        {
            this.device = device;
        }

        #endregion

        #region Methods

        /// <summary>
        /// Gets a shader object
        /// </summary>
        /// <param name="shader">Shader object</param>
        public Effect Get(Shader shader)
        {
            // A little optimazation
            if (shader.Hash == lastUsedHash) return lastUsedEffect;

            Effect effect = null;
            lock (cache)
            {
                if (!cache.TryGetValue(shader.Hash, out effect))
                {
                    // Create shader
                    CompiledEffect compiledEffect = Effect.CompileEffectFromSource(shader.Source, null, null, CompilerOptions.None, Microsoft.Xna.Framework.TargetPlatform.Windows);
                    if (!compiledEffect.Success)
                    {
                         System.Diagnostics.Debug.WriteLine("A shader hasn't compiled: " + compiledEffect.ErrorsAndWarnings);
                         compiledEffect = Effect.CompileEffectFromSource(ErrorShaderSource, null, null, CompilerOptions.None, Microsoft.Xna.Framework.TargetPlatform.Windows);
                    }

                    effect = new Effect(device, compiledEffect.GetEffectCode(), CompilerOptions.None, null);

                    // Put into cache
                    cache.Add(shader.Hash, effect);
                    // And when object will be collected by GC
                    shader.GarbageCollected += new EventHandler(OnShaderGarbageCollected);
                }

                lastUsedHash = shader.Hash;
                lastUsedEffect = effect;
            }

            return effect;
        }

        void OnShaderGarbageCollected(object sender, EventArgs e)
        {
            lock (cache)
            {
                long hash = ((Shader)sender).Hash;
                Effect effect = null;
                if (cache.TryGetValue(hash, out effect))
                {
                    // Remove the effect from 
                    // cache, so it is not valid now
                    cache.Remove(hash);
                    effect.Dispose();                    
                }
            }
        }
        
        #endregion
    }
}
